﻿using System;
using System.Linq;
using Moki.Exceptions;
using Moki;
using Moki.State;

namespace Moki.Core
{
    /// <summary>
    /// Verifies method call. Gets current calls list from mock interceptor. Changes state to veryfing.
    /// </summary>
    internal class Verifier
    {
        private StateChangedNotifier _notifier = new StateChangedNotifier();
        private MockRepository _mockRepository;

        public Verifier(MockRepository mockRepository, StateController stateController)
        {
            stateController.RegisterNotifier(_notifier);
            _mockRepository = mockRepository;
        }

        public void BeginVeryfing()
        {
            _notifier.NotifyStateChanged(MockingState.Verifing);
        }

        public void EndVeryfing()
        {
            _notifier.NotifyStateChanged(MockingState.Acting);
        }

        private string CombineWithCustomMessage(string customMessage, string exceptionMessage)
        {
            if (String.IsNullOrEmpty(customMessage))
                return exceptionMessage;
            else
                return customMessage + Environment.NewLine + exceptionMessage;
        }

        public void VerifyCalledWithAnyArg(object proxy, System.Reflection.MethodInfo method, Func<int, bool> timesConditionChecker)
        {
            VerifyWasCalledWithAnyArg(proxy, method, timesConditionChecker, String.Empty);
        }

        public void VerifyWasCalledWithAnyArg(object proxy, System.Reflection.MethodInfo method, Func<int, bool> timesConditionChecker, string message)
        {
            var calls = _mockRepository.GetInterceptor(proxy).Calls;

            if (!calls.Exists(call => call.Method == method))
                throw new VerifyException(CombineWithCustomMessage(message, String.Format("Method {0} was not called", method.Name)));

            int times = calls.Where(call => call.Method.Name == method.Name)
                .Sum(call => call.Times);

            if (!timesConditionChecker(times))
                throw new VerifyException(CombineWithCustomMessage(message, String.Format("Method {0} was called {1} times", method.Name, times)));
        }

        public void VerifyNotCalled(object proxy, System.Reflection.MethodInfo method)
        {
            VerifyNotCalled(proxy, method, String.Empty);
        }

        public void VerifyNotCalled(object proxy, System.Reflection.MethodInfo method, string message)
        {
            var calls = _mockRepository.GetInterceptor(proxy).Calls;

            if (calls.Exists(call => call.Method.Name == method.Name && call.WasInvoked))
                throw new VerifyException(CombineWithCustomMessage(message, String.Format("{0} was called", method.Name)));
        }

        public void VerifyWasCalled(object proxy, System.Reflection.MethodInfo method, Arguments arguments, Func<int, bool> timesConditionChecker)
        {
            VerifyCalled(proxy, method, arguments, timesConditionChecker, string.Empty);
        }

        public void VerifyCalled(object proxy, System.Reflection.MethodInfo method, Arguments arguments, Func<int, bool> timesConditionChecker, string message)
        {
            var calls = _mockRepository.GetInterceptor(proxy).Calls;

            // Verify with arguments
            if (!calls.Exists(call => call.Method.Name == method.Name && call.WasInvoked && call.Arguments.Matches(arguments)))
                throw new VerifyException(CombineWithCustomMessage(message, String.Format("Method {0} was not called with such arguments", method.Name)));
           
            int times = calls.Where(call => call.Method.Name == method.Name && call.Arguments.Matches(arguments))
                .Sum(call => call.Times);

            if (!timesConditionChecker(times))
                 throw new VerifyException(CombineWithCustomMessage(message, String.Format("Method {0} with such argument was called {1} times", method.Name, times)));  
        }
    }
}
